//
//  SearchesController.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/18.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "SearchesController.h"

#import "Message.h"
#import "Connections.h"
#import "ShareController.h"
#import "SelfFillingMutables.h"
#import "Searches.h"
#import "Shares.h"
#import "SolarSeekEngine.h"
#import "Peers.h"
#import "PeersController.h"
#import "PeerConnectionsController.h"

@class ConcreteSearch;

@interface SearchesController (Concrete)
- (id)searchByAddingResultsFromDecodedSearch:(ConcreteSearch *)concreteSearch;
@end

@interface ConcreteSearch : Search {
	unsigned token;
	NSMutableArray *_results;
	NSMutableArray *_peers;
	BOOL _active;
	int _countPeersWaiting;
	BOOL touched;
}
- (unsigned)token;
- (int)countPeersWaiting;

- (void)waitForPeerForInterval:(NSTimeInterval)time;
- (void)waitForResultsForInterval:(NSTimeInterval)time;

- (void)addShare:(SharePeer *)share;
- (void)addShares:(NSArray *)shares;
- (id)initWithToken:(unsigned)token query:(NSString *)q;
- (id)initWithToken:(unsigned)token query:(NSString *)q peer:(Peer *)p;
@end

@implementation ConcreteSearch

- (BOOL)active { return _active; }
- (int)countPeersWaiting { return _countPeersWaiting; }

- (void)setCountPeersWaiting:(int)peersWaiting { 
	_countPeersWaiting = peersWaiting; 
}

- (void)setActive:(BOOL)active { 
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout) object:nil];
	_active = active; 
	if(_active==NO)
		[self setCountPeersWaiting:0];
}


- (void)timeout {
	[self setActive:NO];
}

- (void)waitForInterval:(NSTimeInterval)time {
	[self setActive:YES];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout) object:nil];
	[self performSelector:@selector(timeout) withObject:nil afterDelay:time];
}

- (void)waitForPeerForInterval:(NSTimeInterval)time { 
	int c = [self countPeersWaiting];
	if(c>=0)
		[self setCountPeersWaiting:c+1];
	[self waitForInterval:time];
}

- (void)waitForResultsForInterval:(NSTimeInterval)time {
	[self setCountPeersWaiting:30];
	[self waitForInterval:time];
}

- (unsigned)token { return token; }

- (id)initWithQuery:(NSString *)query {
	return [self init];
}

- (id)initWithToken:(unsigned)t query:(NSString *)q {
	return [self initWithToken:t query:q peer:nil];
}

- (id)initWithToken:(unsigned)t query:(NSString *)q peer:(Peer *)p {
	if(self = [super initWithQuery:q]) {
		token = t;
		_active = YES;
		_countPeersWaiting = 0;
		_results = [[NSMutableArray alloc] initWithCapacity:50];
		_peers = [[NSMutableArray alloc] initWithCapacity:10];
		touched = NO;
	}
	return self;
}


- (id)initWithCoder:(NSCoder *)decoder {
	if([decoder allowsKeyedCoding]) {
		_active = NO;
		touched = NO;
		_countPeersWaiting = 0;
		self = [super initWithCoder:decoder];
		token = [decoder decodeInt32ForKey:@"token"];
		_results = [[decoder decodeObjectForKey:@"results"] retain];
		NSEnumerator *e = [_results objectEnumerator];
		Share *sp;
		while(sp = [e nextObject]) {
			[[self sharesTreeRoot] addShare:sp];
		}
		if([decoder containsValueForKey:@"peer names"]) {
			NSArray *a = [decoder decodeObjectForKey:@"peer names"];
			NSString *name;
			NSEnumerator *e = [a objectEnumerator];
			_peers = [[NSMutableArray alloc] initWithCapacity:[a count]+5];
			while(name = [e nextObject]) {
				[_peers addObject:[[NSApp engine] peerNamed:name]];
			}
		}
		else //legacy.
			_peers = [[decoder decodeObjectForKey:@"peers"] retain];
	}
	else {
		[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
	}
	return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
	[super encodeWithCoder:coder];
	[coder encodeInt32:token forKey:@"token"];
	[coder encodeObject:[self results] forKey:@"results"];
	NSMutableArray *ma = [NSMutableArray arrayWithCapacity:[_peers count]];
	NSEnumerator *e = [_peers objectEnumerator];
	Peer *p;
	while(p = [e nextObject]) {
		[ma addObject:[p name]];
	}
	[coder encodeObject:ma forKey:@"peer names"];
}

- (void)dealloc {
	[_peers release];
	[_results release];
	[super dealloc];
}

- (void)addShare:(SharePeer *)share { 
	[self addShares:[NSArray arrayWithObject:share]];
}

- (void)addPeers:(NSArray *)shares {
	NSMutableSet *ms = [NSMutableSet setWithCapacity:2];
	NSMutableSet *bad = [NSMutableSet setWithCapacity:2];
	NSEnumerator *e = [shares objectEnumerator];
	SharePeer *sp;
	while(sp = [e nextObject]) {
		Peer *p = [sp peer];
		if([bad containsObject:p]==NO && [ms containsObject:p]==NO) {
			if([_peers containsObject:p]==NO) 
				[ms addObject:p];
			else
				[bad addObject:p];
		}
	}
	[self willChangeValueForKey:@"countPeers"];
	[[self mutableArrayValueForKey:@"peers"] addObjectsFromArray:[ms allObjects]];
	[self didChangeValueForKey:@"countPeers"];


	int c = [self countPeersWaiting];
	if(c > 0) {	//we are waiting for more peers so remove them.
		c = c  - [ms count];
		c = c<0?0:c;	//make sure we don't go below zero.
		[self setCountPeersWaiting:c];
		if(c==0)
			[self setActive:NO];
	}
}

- (void)addShares:(NSArray *)shares {
	if(touched == NO) {
		@synchronized(self) {
			touched = YES;
		}
	}
	[self willChangeValueForKey:@"countResults"];
	NSEnumerator *e = [shares objectEnumerator];
	SharePeer *sp;
	while(sp = [e nextObject]) {
		[[self sharesTreeRoot] addShare:sp];
	}
	[self addPeers:shares];
	[self didChangeValueForKey:@"countResults"];	
}

- (NSArray *)results { 
	if(touched) {
		@synchronized(self) {
			if(touched) {
				[self willChangeValueForKey:@"results"];
				NSArray *a = [[self sharesTreeRoot] sharesThisAndSubLevels];
				[_results setArray:a];
				touched=NO;
				[self didChangeValueForKey:@"results"];
			}
		}
	}
	return _results;
}
- (unsigned)countOfResults { return [[self results] count]; }
- (id)objectInResultsAtIndex:(unsigned)index { return [[self results] objectAtIndex:index]; }
- (void)getResults:(id *)buffer range:(NSRange)range {
	[[self results] getObjects:buffer range:range];
}


#pragma KVO/C

- (unsigned)countResults { return [[self results] count]; }
- (unsigned)countPeers { return [_peers count]; }

- (NSArray *)peers { return _peers; }
- (void)insertObject:(id)o inPeersAtIndex:(unsigned)index { [_peers insertObject:o atIndex:index]; }
- (void)removeObjectFromPeersAtIndex:(unsigned)index { [_peers removeObjectAtIndex:index]; }

@end

#pragma mark -


@implementation SearchesController

- (id)init {
	[NSException raise:@"D'OHException" format:@"Please call the right initializer."];
	[self release];
	return nil;
}

- (id)initWithEngine:(SolarSeekEngine *)engine {
	if(self = [super init] ) {
		_engine = engine;
		_serverConnection = [[engine serverConnection] retain];
		_shareController = [[engine shareController] retain];
		[_shareController addObserver:self forKeyPath:@"sharesPeerReceived" options:nil context:nil];
		_searchesDictionary = [[NSMutableDictionary alloc] init];
		_searchesPending = [[NSMutableDictionary alloc] init];
		_searchesArray = [[NSMutableArray alloc] init];
		_tokensDead = [[NSMutableSet alloc] init];
	}
	return self;
}

- (void)dealloc {
	[_searchesArray release];
	[_serverConnection release];
	[_shareController removeObserver:self forKeyPath:@"sharesPeerReceived"];
	[_shareController release];
	[_searchesDictionary release];
	[_tokensDead release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
/*
	NSArray *a = [[NSArray alloc] initWithObjects:keyPath,object,change,[NSNumber numberWithLong:(long)context],nil];
	[self performSelectorOnMainThread:@selector(observe:) withObject:a waitUntilDone:YES];
	[a release];
}

- (void)observe:(NSArray *)observeArray {
	NSString *keyPath = [observeArray objectAtIndex:0];
	id object = [observeArray objectAtIndex:1];
	NSDictionary *change = [observeArray objectAtIndex:2];
	void *context = (void *)[[observeArray objectAtIndex:3] longValue];
*/	
	NSAssert1(object==_shareController,@"Observed wrong object %@.",object);
	NSAssert1([keyPath isEqual:@"sharesPeerReceived"],@"Observed wrong keyPath %@.",keyPath);
	
	NSEnumerator *e = [[_shareController sharesPeerReceived] objectEnumerator];
	SharePeer *sp;
	while(sp = [e nextObject]) {
		id t = [sp valueForKey:@"token"];	
		NSMutableArray *set = [_searchesPending objectForKey:t];
		if(set == nil && [_tokensDead containsObject:t]==NO) {
			set = [NSMutableArray array];
			[_searchesPending setObject:set forKey:t];
		}
		else if([_tokensDead containsObject:t]==YES)
			continue;
		[set addObject:sp];
		[self performSelector:@selector(addSharesForToken:) withObject:t afterDelay:0.5];
	}
}

- (ConcreteSearch *)searchWithWords:(NSString *)words alternate:(ConcreteSearch *)alt {
	ConcreteSearch *cs = [_searchesDictionary objectForKey:words];
	if(cs == nil) {
		cs = alt!=nil?[alt retain]:[[ConcreteSearch alloc] initWithToken:[_serverConnection tokenNext] query:words];
		//fejta: TODO: HIGH: why doesn't this work??  we get an "Unknown Search"
		[_searchesDictionary setObject:cs forKey:words];
		[_searchesDictionary setObject:cs forKey:[NSNumber numberWithUnsignedInt:[cs token]]];
		[[self mutableArrayValueForKey:@"searches"] addObject:cs]; //searches will probably be low so slow is okay
		[cs release];
	}
	return cs;
}	


- (ConcreteSearch *)searchWithWords:(NSString *)words { [self searchWithWords:words alternate:nil]; }

- (void)addSharesForToken:(NSNumber *)n {
	ConcreteSearch *cs = [_searchesDictionary objectForKey:n];
	if(cs == nil) {
		cs = [self searchWithWords:[NSString stringWithFormat:@"Unknown Search %d",[n unsignedIntValue]]];
	}
	[cs addShares:[_searchesPending objectForKey:n]];
	[SearchesController cancelPreviousPerformRequestsWithTarget:self selector:@selector(addSharesForToken:) object:n];
	[_searchesPending removeObjectForKey:n];
}

- (void)browseUser:(NSString *)name {
	Peer *peer = [[NSApp engine] peerNamed:name];
	[_tokensDead addObject:[NSNumber numberWithUnsignedInt:[peer token]]];
//	[_tokensDead removeObject:[NSNumber numberWithUnsignedInt:[peer token]]];
//	ConcreteSearch *cs2 = [[ConcreteSearch alloc] initWithToken:[peer token] query:name];
//	ConcreteSearch *cs = [self searchWithWords:name alternate:cs2];
//	[cs2 release];
	Message *m = [Message messageWithCode:GetSharedFileList];
	[[[[NSApp engine] peerConnectionsController] peerConnectionForPeer:peer] sendMessage:m];
//	[[cs mutableArrayValueForKey:@"peers"] removeObject:peer];
//	[cs waitForPeerForInterval:240.0];
//	return cs;
}

- (Search *)searchUser:(NSString *)name forWords:(NSString *)words {
	ConcreteSearch *cs = [self searchWithWords:words];	
	Message *m = [Message messageWithCode:PeerFileSearch];
	[m putInt:[cs token]];
	[m putString:words];
	[[[_engine peerConnectionsController] peerConnectionForName:name] sendMessage:m];
	[[cs mutableArrayValueForKey:@"peers"] removeObject:[[NSApp engine] peerNamed:name]];
	[cs waitForPeerForInterval:60.0];
	return cs;
}

- (Search *)searchForWords:(NSString *)words {
	ConcreteSearch *cs = [self searchWithWords:words];
	Message *m = [Message messageWithCode:FileSearch];
	[m putInt:[cs token]];
	[m putString:words];
	[_serverConnection sendMessage:m];
	[cs waitForResultsForInterval:300.0];
	return cs;
}

- (void)endSearch:(Search *)search {
	if(search==nil || [search isKindOfClass:[ConcreteSearch class]]==NO)
		return;
	ConcreteSearch *cs = search;
	[_tokensDead addObject:[NSNumber numberWithUnsignedInt:[cs token]]];
	[_searchesDictionary removeObjectForKey:[NSNumber numberWithUnsignedInt:[cs token]]];
	[_searchesDictionary removeObjectForKey:[cs query]];
	[[self mutableArrayValueForKey:@"searches"] removeObject:cs];
} 

- (NSArray *)searches { return _searchesArray; }
- (void)insertObject:(id)o inSearchesAtIndex:(unsigned)index { [_searchesArray insertObject:o atIndex:index]; }
- (void)removeObjectFromSearchesAtIndex:(unsigned)index { [_searchesArray removeObjectAtIndex:index]; }


- (id)searchByAddingResultsFromDecodedSearch:(ConcreteSearch *)concreteSearch {
	ConcreteSearch *cs = [self searchWithWords:[concreteSearch query] alternate:concreteSearch];
	if(cs != concreteSearch) {
		NSSet *old = [NSSet setWithArray:[cs results]];
		NSMutableSet *new = [NSMutableSet setWithArray:[concreteSearch results]];
		[new minusSet:old];
		
		NSNumber *t = [NSNumber numberWithUnsignedInt:[cs token]];
		NSMutableArray *set = [_searchesPending objectForKey:t];
		if(set == nil) {
			set = [NSMutableArray array];
			[_searchesPending setObject:set forKey:t];
		}
		[set addObjectsFromArray:[new allObjects]];
		[self performSelector:@selector(addSharesForToken:) withObject:t afterDelay:0.5];
	}
	return cs;
}

- (void)saveSearch:(Search *)search toFile:(NSString *)file {
	[NSKeyedArchiver archiveRootObject:search toFile:file];
}

- (Search *)loadSearchFromFile:(NSString *)file {
	ConcreteSearch *search = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
	return [self searchByAddingResultsFromDecodedSearch:search];
}


@end
